마이크로서비스 아키텍처(MSA)에서 트랜잭션 처리 방법
MSA에서는 각 마이크로서비스가 독립적인 데이터베이스를 갖고 있기 때문에, 전통적인 모놀리식 시스템처럼 여러 서비스에 걸쳐 하나의 원자적(ACID) 트랜잭션을 적용할 수 없습니다. 따라서 여러 서비스에 분산된 트랜잭션을 관리하는 것은 매우 중요한 과제입니다. 아래는 면접에서 설명할 때 활용할 수 있는 상세 내용입니다.
MSA에서 분산 트랜잭션이 어려운 이유
- 각 서비스가 독립적이며 자체 DB를 소유해, 서비스 간 전통적인 ACID 트랜잭션 불가
- 서비스 간 결합도를 낮추고, 확장성과 내결함성을 유지하면서 데이터 일관성 보장 필요
- 장시간 리소스 락을 걸지 않고, 성능 저하 없이 여러 서비스 트랜잭션을 조율해야 함
MSA에서 트랜잭션을 처리하는 대표 패턴
1. Saga 패턴 (가장 많이 사용되고 권장됨)
개념:
- 분산 트랜잭션 전체를 여러 단계의 로컬 트랜잭션으로 나눔 (서비스별 1개씩).
- 각 로컬 트랜잭션 완료 시 이벤트를 발행하거나 다음 서비스 호출.
- 만약 중간에 실패하면 이전 단계에서 완료된 작업을 취소하는 보상 트랜잭션을 실행.
- 결과적으로 전체 작업이 성공하거나 최종적으로 롤백됨.
Saga 조율 방식:
- 오케스트레이션: 중앙 조정자가 각 서비스 작업 순서 및 성공/실패를 관리하고, 실패 시 보상 트랜잭션을 트리거함.
- 코레오그래피: 중앙 조정자 없이, 서비스들이 서로 이벤트를 감지하여 다음 작업 또는 보상으로 반응함.
사례 예시:
- 주문 서비스가 주문을 생성 → 결제 서비스가 결제를 진행 → 재고 서비스가 재고 차감 → 모두 정상 종료
2. 2단계 커밋(Two-Phase Commit, 2PC)
개념:
- 중앙 조정자가 모든 관련 서비스에게 커밋을 준비할 수 있는지 물어보고(prepare), 모두 승낙 시 커밋 지시, 아니면 롤백 지시.
장점:
- 엄격한 ACID 데이터 일관성 보장.
단점:
- 리소스 잠금 발생, 지연과 교착 상태 위험성 있음.
- MSA 환경에서 확장성 낮고 무겁기 때문에 잘 쓰이지 않음.
3. 트랜잭셔널 아웃박스(Transactional Outbox) 및 이벤트 기반 처리
- 서비스가 자신의 DB에 상태 변경과 이벤트 메시지를 같은 로컬 트랜잭션 내에서 저장.
- 이후 별도의 프로세스가 이벤트를 비동기로 외부에 발행.
- 점진적 일관성을 제공하며, 서비스 간 느슨한 결합과 확장성에 적합.
면접 요약표
| 패턴 | 설명 | 조율 방식 | 장점 | 단점 |
|---|---|---|---|---|
| Saga | 로컬 트랜잭션 연속 실행 및 보상 트랜잭션 | 오케스트레이션 / 코레오그래피 | 확장성 좋고, 느슨한 결합, 점진적 일관성 | 보상 트랜잭션 설계 복잡 |
| 2단계 커밋 (2PC) | 중앙 조정자에 의한 전역 커밋/롤백 결정 | 중앙 조정자 | 엄격한 ACID 보장 | 블로킹, 지연, 확장 어려움 |
| 트랜잭셔널 아웃박스 | DB 변경과 이벤트 동시 저장 및 비동기 발행 | 이벤트 기반 | 느슨한 결합과 비동기 처리 | 점진적 일관성, 인프라 복잡도 |
면접에서 이렇게 설명하세요
- MSA에서는 전통적인 ACID 트랜잭션이 불가능함을 먼저 강조하세요.
- 가장 널리 쓰이는 방법은 Saga 패턴으로, 단계별 로컬 트랜잭션 수행과 보상 트랜잭션으로 일관성 확보한다고 설명하세요.
- 2PC는 엄격하지만 확장성과 성능 문제 때문에 요즘은 잘 사용하지 않는다고 언급하면 좋습니다.
- 메시지와 이벤트를 이용한 비동기, 점진적 일관성 접근법도 소개하세요.
- 간단한 실제 사례(주문-결제-재고)로 설명하면 이해가 쉽습니다.
- 일관성과 확장성 사이 트레이드오프도 간단히 이야기하세요.
- 필요하면 중앙 집중 Saga 조정자나 이벤트 버스 기반 코레오그래피 설명도 가능합니다.
분산 트랜잭션 관리: 마이크로서비스에서의 SAGA 패턴 설명 (Spring Boot 예제 포함)
간단 요약
마이크로서비스 아키텍처에서는 각 서비스가 독립적인 데이터베이스를 가지므로, 전통적인 ACID(원자성) 트랜잭션을 서비스 간에 적용하기 어렵습니다.
SAGA 패턴은 마이크로서비스에서 분산 트랜잭션을 처리하는 대표적인 방법으로, 로컬 트랜잭션을 순차적으로 실행하고 실패 시 보상 트랜잭션으로 복구하여 데이터 일관성을 유지합니다.
핵심 설명 내용
- Saga 패턴: 분산 트랜잭션을 여러 단계의 로컬 트랜잭션으로 나눕니다. 각 서비스가 자신의 트랜잭션을 수행하고, 이벤트를 발행합니다. 실패 시에는 이전 단계들을 보상 트랜잭션으로 취소합니다.
- 조정 방식:
- 오케스트레이션: 중앙 조정자가 각 서비스의 수행을 제어하고 실패 시 보상 트랜잭션을 트리거합니다.
- 코레오그래피: 중앙 조정자 없이 서비스들이 서로 이벤트를 듣고 다음 단계를 자동으로 실행하거나 보상합니다.
SAGA 오케스트레이션 작동 방식 (워크플로우 및 다이어그램)
- 사용자가 주문 등 초기 작업을 시작합니다.
- 오케스트레이터 서비스가 단계별로 각 마이크로서비스를 호출합니다 (예: 주문 → 결제 → 재고).
- 중간에 실패가 발생하면 이전 작업에 대한 보상 트랜잭션을 실행합니다.
- 전체 작업이 성공하거나 완전한 롤백(복구) 상태에 도달합니다.
Spring Boot 예제: Saga 오케스트레이터 (오케스트레이션 방식)
1. 주문 서비스
@Service
public class BookingService {
@Autowired private JmsTemplate jmsTemplate;
@Autowired private BookingRepository bookingRepository;
@Transactional
public void makeBooking(Booking booking) {
bookingRepository.save(booking); // 로컬 트랜잭션 실행
jmsTemplate.convertAndSend("bookingQueue", booking); // 오케스트레이터에 이벤트 발행
}
@Transactional
public void confirmBooking(Long bookingId) { /* ... */ }
@Transactional
public void cancelBooking(Long bookingId) { /* 보상 트랜잭션 로직 */ }
}
2. 결제 서비스
@Service
public class PaymentService {
@Autowired private JmsTemplate jmsTemplate;
@Autowired private PaymentRepository paymentRepository;
@Transactional
public void processPayment(Booking booking) {
Payment payment = new Payment();
payment.setBookingId(booking.getId());
// 기타 필드 설정
paymentRepository.save(payment);
jmsTemplate.convertAndSend("paymentQueue", payment);
}
@Transactional
public void confirmPayment(Long bookingId) { /* ... */ }
@Transactional
public void cancelPayment(Long bookingId) { /* 보상 트랜잭션 로직 */ }
}
3. Saga 오케스트레이터
@Service
public class SagaOrchestrator {
@Autowired private BookingService bookingService;
@Autowired private PaymentService paymentService;
@JmsListener(destination = "bookingQueue")
public void handleBooking(Booking booking) {
// 결제 처리 또는 보상 시작
}
@JmsListener(destination = "paymentQueue")
public void handlePayment(Payment payment) {
try {
paymentService.confirmPayment(payment.getBookingId());
bookingService.confirmBooking(payment.getBookingId());
} catch (Exception e) {
// 보상 트랜잭션 동작
bookingService.cancelBooking(payment.getBookingId());
paymentService.cancelPayment(payment.getBookingId());
}
}
}
- 서비스 간 메시징(ActiveMQ, Kafka, RabbitMQ 등)으로 통신합니다.
- 보상 트랜잭션은 이전 성공된 로컬 트랜잭션을 취소하는 리버스 작업입니다.
인터뷰용 일반적 워크플로우
| 단계 | 오케스트레이터 행동 | 성공 시 | 실패 시 |
|---|---|---|---|
| 1. 주문 생성 | 주문 서비스 호출 | 다음 단계 진행 | 실패 응답, 보상 없음 |
| 2. 결제 | 결제 서비스 호출 | 다음 단계 진행 | 보상: 주문 취소 |
| 3. 재고 | 재고 서비스 호출 | 주문 완료 | 보상: 결제 환불, 주문 취소 |
보상 트랜잭션 예
- 결제가 성공했지만, 재고 확보가 실패하면 다음과 같은 절차가 있습니다:
- 결제 환불 (결제 서비스 보상)
- 주문 취소 (주문 서비스 보상)